﻿Public Class ExpandablePanel : Inherits Library.ScrollableControl

	Public Boxes As New List(Of Box)

	Sub New()
		HScroll.Visible = False
		VirtualSize = New Size(ClientRectangle.Width, 5)
		VScroll.SmallChange = 16
		ResizeRedraw = True
		DoubleBuffered = True
	End Sub

	Dim _HotItem As Line
	Property HotItem As Line
		Get
			Return _HotItem
		End Get
		Set(ByVal Value As Line)
			If _HotItem Is Value Then Return
			If Not HotItem Is Nothing Then Invalidate(HotItem.Rect)
			_HotItem = Value
			If Not HotItem Is Nothing Then Invalidate(HotItem.Rect)
		End Set
	End Property

	Dim _SelectedItem As Line
	Property SelectedItem As Line
		Get
			Return _SelectedItem
		End Get
		Set(ByVal Value As Line)
			If _SelectedItem Is Value Then Return
			If Not SelectedItem Is Nothing Then Invalidate(SelectedItem.Rect)
			_SelectedItem = Value
			If Not SelectedItem Is Nothing Then Invalidate(SelectedItem.Rect)
			RaiseEvent SelectedItemChanged(Me, Value)
		End Set
	End Property

	Event SelectedItemChanged(ByVal sender As Object, ByVal Line As Line)

	Dim _DropTarget As Line
	Property DropTarget As Line
		Get
			Return _DropTarget
		End Get
		Set(ByVal Value As Line)
			If _DropTarget Is Value Then Return
			If Not DropTarget Is Nothing Then Invalidate(DropTarget.Rect)
			_DropTarget = Value
			If Not DropTarget Is Nothing Then Invalidate(DropTarget.Rect)
		End Set
	End Property

	Function AddBox(ByVal Text As String) As Box
		Dim Box As New Box
		Box.Title = Text
		Box.Parent = Me
		Boxes.Add(Box)
		Measure()
		Return Box
	End Function

	Protected Overrides Sub OnMouseDown(ByVal e As System.Windows.Forms.MouseEventArgs)
		For Each Box In Boxes
			If Box.Rect.Contains(e.Location) Then
				Box.OnMouseDown(e)
			End If
		Next
		MyBase.OnMouseDown(e)
	End Sub

	Protected Overrides Sub OnMouseMove(ByVal e As System.Windows.Forms.MouseEventArgs)
		MyBase.OnMouseMove(e)
		For Each Box In Boxes
			If Box.Rect.Contains(e.Location) Then
				Box.OnMouseMove(e)
			End If
		Next
		If HotItem Is Nothing Then Cursor = Cursors.Default Else Cursor = Cursors.Hand
	End Sub

	Protected Overrides Sub OnMouseDoubleClick(ByVal e As System.Windows.Forms.MouseEventArgs)
		MyBase.OnMouseDoubleClick(e)
		For Each Box In Boxes
			If Box.Rect.Contains(e.Location) Then
				Box.OnMouseDoubleClick(e)
			End If
		Next
	End Sub

	Protected Overrides Sub OnMouseLeave(ByVal e As System.EventArgs)
		MyBase.OnMouseLeave(e)
		HotItem = Nothing
	End Sub

	Public Sub Measure()
		If Boxes Is Nothing Then Exit Sub
		Dim y As Integer = 4
		For Each Box In Boxes
			Box.Measure(y, ClientRectangle.Width)
			y += Box.Height + 4
		Next
		VirtualSize = New Size(ClientRectangle.Width, y)
		Invalidate()
	End Sub

	Protected Overrides Sub OnSizeChanged(ByVal e As System.EventArgs)
		MyBase.OnSizeChanged(e)
		Measure()
	End Sub

	Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
		MyBase.OnPaint(e)
		e.Graphics.Clear(SystemColors.ButtonShadow)
		Dim ClipRectangle = e.ClipRectangle
		ClipRectangle.Offset(HScroll.Value, VScroll.Value)
		For Each Box In Boxes
			If Box.Rect.IntersectsWith(ClipRectangle) Then
				Box.Draw(e.Graphics)
			End If
		Next
	End Sub

	Class Box

		Public Title As String
		Public Rect As Rectangle
		Public HeaderRect, ExpandRect As Rectangle
		Public Parent As ExpandablePanel
		Public Lines As New SortedList(Of String, Line)

		Dim _Expanded As Boolean
		Property Expanded As Boolean
			Get
				Return _Expanded
			End Get
			Set(ByVal value As Boolean)
				_Expanded = value
				Parent.Measure()
			End Set
		End Property

		Sub Measure(ByVal y As Integer, ByVal Width As Integer)
			HeaderRect = New Rectangle(4, y, Width - 9, 23)
			ExpandRect = New Rectangle(HeaderRect.Right - HeaderRect.Height, HeaderRect.Top, HeaderRect.Height, HeaderRect.Height)
			y += HeaderRect.Height
			If Expanded And Lines.Count > 0 Then
				y += 1
				For Each Line In Lines.Values
					Line.Measure(y, Width)
					y += Line.Height
				Next
				'y += 1
			End If
			Rect = New Rectangle(4, HeaderRect.Top, Width - 9, y - HeaderRect.Top)
		End Sub

		ReadOnly Property Height As Integer
			Get
				Return Rect.Height
			End Get
		End Property

		Sub Draw(ByVal g As Graphics)
			g.SmoothingMode = Drawing2D.SmoothingMode.HighQuality
			g.FillRectangle(SystemBrushes.ButtonFace, Rect)
			g.FillRectangle(LGBv(HeaderRect, Color.Black.Opacity(128), Color.Black.Opacity(128)), HeaderRect)
			Dim R = ExpandRect
			R.Inflate(-5, -5)
			g.PixelOffsetMode = Drawing2D.PixelOffsetMode.HighQuality
			g.FillEllipse(Brushes.White, R)
			R.Inflate(-3, -3)
			If Expanded Then
				R.Inflate(0, -1)
				R.Offset(0, 1)
				Dim P As New Drawing2D.GraphicsPath
				P.AddLines(New Point() {New Point(R.Left, R.Top), New Point(R.Right, R.Top), New Point(R.Left + R.Width \ 2, R.Bottom)})
				g.FillPath(Brushes.Black, P)
				For Each Line In Lines.Values
					Line.Draw(g)
				Next
			Else
				R.Inflate(-1, 0)
				R.Offset(-1, 0)
				Dim P As New Drawing2D.GraphicsPath
				P.AddLines(New Point() {New Point(R.Right, R.Top), New Point(R.Right, R.Bottom), New Point(R.Left, R.Top + R.Height \ 2)})
				g.FillPath(Brushes.Black, P)
			End If
			g.PixelOffsetMode = Drawing2D.PixelOffsetMode.None
			g.DrawRectangle(Color.White.Opacity(255).Pen, Rect)
			R = HeaderRect
			R.Inflate(-2, 0)
			g.DrawText(Title, Parent.Font, R, Color.White, Windows.Forms.TextFormatFlags.PreserveGraphicsTranslateTransform Or TextFormatFlags.VerticalCenter)
		End Sub

		Sub AddLine(ByVal Name As String, ByVal Text As String, ByVal Image As Image)
			Dim Line As New Line
			Line.Name = Name
			Line.Text = Text
			Line.Parent = Parent
			Line.Box = Me
			Line.Image = Image
			Lines.Add(Name, Line)
			Parent.Measure()
		End Sub

		Sub Remove(ByVal Name As String)
			Lines.Remove(Name)
			Parent.Measure()
		End Sub

		Sub OnMouseDown(ByVal e As MouseEventArgs)
			If HeaderRect.Contains(e.Location) Then
				If ExpandRect.Contains(e.Location) Then
					Expanded = Not Expanded
				End If
			ElseIf Expanded Then
				For Each Line In Lines.Values
					If Line.Rect.Contains(e.Location) Then
						Line.OnMouseDown(e)
					End If
				Next
			End If
		End Sub

		Sub OnMouseMove(ByVal e As MouseEventArgs)
			If HeaderRect.Contains(e.Location) Then
				Parent.HotItem = Nothing
			ElseIf Expanded Then
				For Each Line In Lines.Values
					If Line.Rect.Contains(e.Location) Then
						Line.OnMouseMove(e)
					End If
				Next
			End If
		End Sub

		Sub OnMouseDoubleClick(ByVal e As MouseEventArgs)
			If HeaderRect.Contains(e.Location) Then
				Expanded = Not Expanded
			End If
		End Sub

		Function HitTest(ByVal Point As Point) As Line
			For Each Line In Lines.Values
				If Line.Rect.Contains(Point) Then Return Line
			Next
			Return Nothing
		End Function

	End Class

	Class Line

		Public Parent As ExpandablePanel
		Public Name As String
		Public Text As String
		Public Rect As Rectangle
		Public Image As Image
		Public Box As Box

		Dim _Count As String
		Property Count As String
			Get
				Return _Count
			End Get
			Set(ByVal Value As String)
				_Count = Value
				Parent.Invalidate(Rect)
			End Set
		End Property
		Sub Measure(ByVal y As Integer, ByVal Width As Integer)
			Rect = New Rectangle(5, y, Width - 10, 21)
		End Sub

		ReadOnly Property Height As Integer
			Get
				Return Rect.Height
			End Get
		End Property

		Sub Draw(ByVal g As Graphics)
			'g.FillRectangle(Brushes.White, Rect)
			Dim R = Rect
			R.Width -= R.Height - 2
			R.Offset(R.Height - 2, 0)
			Dim w = g.MeasureText(Text, Parent.Font, R.Size, Windows.Forms.TextFormatFlags.NoPadding Or Windows.Forms.TextFormatFlags.PreserveGraphicsTranslateTransform Or TextFormatFlags.VerticalCenter).Width
			Dim Col As Color
			If Me Is Parent.SelectedItem Or Me Is Parent.DropTarget Then
				g.FillRectangle(Color.Black.Opacity(192).Brush, Rect)
				Col = Color.White
			ElseIf Me Is Parent.HotItem Then
				g.FillRectangle(Color.Black.Opacity(32).Brush, Rect)
				Col = SystemColors.ControlDarkDark
			Else
				Col = SystemColors.ControlDarkDark
			End If
			g.DrawText(Text, Parent.Font, R, Col, Windows.Forms.TextFormatFlags.PreserveGraphicsTranslateTransform Or TextFormatFlags.VerticalCenter)
			R.Offset(w + 2, 0)
			If Count <> "" Then
				If Me Is Parent.SelectedItem Or Me Is Parent.DropTarget Then
					Col = Color.Yellow
				ElseIf Me Is Parent.HotItem Then
					Col = Color.SlateBlue
				Else
					Col = Color.SlateBlue
				End If
				g.DrawText("(" & Count & ")", Parent.Font, R, Col, Windows.Forms.TextFormatFlags.PreserveGraphicsTranslateTransform Or TextFormatFlags.VerticalCenter)
			End If
			R = Rect
			R.Width = R.Height
			R.Inflate(-2, -2)
			g.DrawImage(Image, R)
		End Sub

		Sub OnMouseMove(ByVal e As MouseEventArgs)
			Parent.HotItem = Me
		End Sub

		Sub OnMouseDown(ByVal e As MouseEventArgs)
			Parent.SelectedItem = Me
		End Sub

	End Class

	Function HitTest(ByVal Point As Point) As ExpandablePanel.Line
		For Each Box In Boxes
			If Box.Rect.Contains(Point) Then Return Box.HitTest(Point)
		Next
		Return Nothing
	End Function

End Class


